
<template>
    <el-tabs v-model="activeName" v-if="isTabs">
        <el-tab-pane v-for="(item, index) in pageConfig" :key="index" :label="item.title" :name="index">
            <template v-for="ele in Object.keys(item)">
                <el-col :span="24" v-if="activeName === index" :key="ele">
                    <component :ref="comp => bindComp(comp, ele)" :is="item[ele]?.component" :schema=item[ele] :data="data" @up="handleUp" @fetch="handleFetch" @event="handleEvent" :queryModel="queryModel" :querySlotIdx="querySlotIdx" :columnSlotIdx="columnSlotIdx" :operationSlotIdx="operationSlotIdx">
                        <template #tableColumn="slotProps">
                            <slot name="tableColumn" :row="slotProps.row"></slot>
                        </template>
                        <template #queryItem="slotProps">
                            <slot name="queryItem" :item="slotProps.item"></slot>
                        </template>
                        <template #operationItem="slotProps">
                            <slot name="operationItem" :item="slotProps.item"></slot>
                        </template>
                    </component>
                </el-col>
            </template>
            <slot></slot>
        </el-tab-pane>
    </el-tabs>
    <el-row v-else>
       <template v-for="(item, idx) in pageConfig" :key="idx">
            <el-col :span="24" v-for="ele in Object.keys(item)" :key="ele">
                <component :ref="comp => bindComp(comp, ele)" :is="item[ele]?.component" :schema=item[ele] :data="data" @up="handleUp" @fetch="handleFetch" @event="handleEvent" :queryModel="queryModel" :querySlotIdx="querySlotIdx" :columnSlotIdx="columnSlotIdx" :operationSlotIdx="operationSlotIdx">
                    <template #tableColumn="slotProps">
                        <slot name="tableColumn" :row="slotProps.row"></slot>
                    </template>
                    <template #queryItem="slotProps">
                        <slot name="queryItem" :item="slotProps.item"></slot>
                    </template>
                    <template #operationItem="slotProps">
                        <slot name="operationItem" :item="slotProps.item"></slot>
                    </template>
                </component>
            </el-col>
        </template>
        <slot></slot>
  </el-row>
  
</template>
<!-- 使用組合式api, setup 的形式，在插件開發中，存在註冊問題： [Vue warn]: Component "undefined" has already been registered in target app.
    無默認導出，組件註冊時，組件為undefined
以下方式可快速解決
-->
<script>
/* 组装配置数据，渲染页面。处理兄弟组件间的通信 */
import v3Table from './widget/v3Table.vue'
import v3Query from './widget/v3Query.vue'
import v3Dialog from './widget/v3Dialog.vue'
import v3Operation from './widget/v3Operation.vue'
import { isArray, mergeArray } from '@/utils/tools'
import { ref, computed, reactive, markRaw, onMounted, nextTick } from 'vue'
import { factory } from '@/utils/factory'
import { usePageInfo } from '@/hooks/pageInfo'
import { da } from 'element-plus/es/locales.mjs'
export default {
    name: "v3Crud",
    props: {
        schema: {
            type: [Object, Array],
            default: () => []
        },
        queryModel: {
            type: Object,
            default: () => {
                a: 1
            }
        },
        dialogModel: {
            type: Number,
            default: 0
        },
        columnSlotIdx: {
            type: [Number, Array],
            default: 0
        },
        querySlotIdx: {
            type: [Number, Array],
            default: 0
        },
        operationSlotIdx: {
            type: [Number, Array],
            default: 0
        },
        hooks: {
            type: Object,
            default: () => {}
        },
        data: {
            type: Array,
            default: () => []
        }
    },
    setup(props, { emit }) {
        onMounted(() => {
            emit('ready', true)
        })
        /* v3Page接收数组schema时，展示el-tabs,否则Object时，单页面展示 */
        const { schema, hooks } = props
        // console.log('schema---', schema)
        const widgetExpose = reactive({})
        const activeName = ref(0)
        const { pageInfo } = usePageInfo()
        // console.log(pageInfo._query_)
        const pageConfig = reactive([])
        const fieldSort = {} // 根据showIn数组中的字段，将fields分类，用于展示在不同方法中，如：query、table、add、edit 配合 attr 属性使用
        /* 生成各子组件的结构数据 */
        const makeSchema = (configItem, i) => {
            const globalFields = configItem.fields
            let tableSchema = {
                index: i,
                component: markRaw(v3Table),
                columns: [],
                ...configItem?.config?.table
            }
            let querySchema = {
                index: i,
                component: markRaw(v3Query),
                items: [],
                matchType: configItem?.config?.query?.matchType || {}, // 查询组件中，记录字段的查询数据库时的匹配类型：btween、in、eq、like、=、>、<、>=、<=、!= */
                ...configItem?.config?.query
            }
            let dialogSchema = {
                index: i,
                component: markRaw(v3Dialog),
                items: [],
                ...configItem?.config?.dialog
            }
            let operationSchema = {
                index: i,
                component: markRaw(v3Operation),
                items: [],
                ...configItem?.config?.operation
            }
            globalFields.length && globalFields.forEach(field => {
                /* showLogic：根据登录信息和页面断言信息(地址栏传入的数据)判断 */
                field.isShow = factory.validateByEval(field.showLogic, {...pageInfo?._assert_, ...pageInfo?._storage_})
                const [f, type] = field.prop?.split(':')
                if (field.showIn?.includes('query')) {
                    if (type) {
                        field.prop = field.config?.reqField || f
                        querySchema.matchType[f] = type
                    }
                    querySchema.items.push(field)
                }
                /* 配置中，无showIn，或者showIn中包含table，则添加到table中 */
                if (!field.showIn || field.showIn?.includes('table')) {
                    f && (field.prop = f)
                    tableSchema.columns.push(field)
                }
                /* 支持全局fields在自定义fields中复用 */
                field.showIn?.length && field.showIn.forEach(attr => {
                    if (attr !== 'table' && attr !== 'query') {
                        const key = `${attr}_${configItem.title}`
                        !fieldSort[key] && (fieldSort[key] = [])
                         fieldSort[key].push({...field})
                    }
                })
            })
            // console.log('fieldSort---', fieldSort)
            // 处理查询组件的matchType---上面修改了 prop 的结构，所以这里保存下匹配模式
            if (configItem.config) configItem.config['query'] = { 'matchType' : querySchema.matchType }
            // 添加操作按钮
            const operationKeys = Object.keys(configItem.operations || {})
            operationKeys && operationKeys.forEach(key => {
                /* showLogic：根据登录信息和页面断言信息(地址栏传入的数据)判断 */
                showOperationBtn(configItem.operations[key], true, configItem.title)
                if (configItem.operations[key]?.length) {
                    if (key === 'default') {
                        operationSchema.items.push(...configItem.operations[key])
                    }
                    key === 'query' && Object.assign(querySchema, {'operations': configItem.operations[key], 'tag': 'button', urlQueryData: pageInfo._query_})
                    key === 'table' && tableSchema.columns.push({'operations': configItem.operations[key], "label": '操作', isShow: true, type: 'operation'})
                    // key === 'dialog' && dialogSchema.items.push({'operations': item.operations[key], 'tag': 'button'})
                }
            })
            // tab下页面显示逻辑，默认全部显示
            configItem.showLogic = configItem.showLogic ? factory.validateByEval(configItem.showLogic, {...pageInfo?._assert_, ...pageInfo?._storage_}) : true
            configItem.showLogic && pageConfig.push({title: configItem?.title, querySchema, operationSchema, tableSchema, dialogSchema })
            // console.log('schema---schema---', schema)
        }
        
        /* 递归处理显示逻辑 */
        const showOperationBtn = (arr, isLogic, type) => {
            const context = JSON.parse(JSON.stringify({...pageInfo?._storage_, ...pageInfo?._assert_}))
            arr?.length && arr.forEach(item => {
                isLogic && (item.isShow = factory.validateByEval(item.showLogic || item.showLogic, context))
                if (item) {
                    if (item.operations) {
                        showOperationBtn(item.operations, true, type)
                    }
                    /* 1，当前按钮展示需考虑下一级按钮展示问题。2，如果当前无操作列表，无需判断 */
                    if ((item.next && item.isShow) || (!item.operations && !item.isShow && !item.isCustom)) {
                        item?.next && showOperationBtn([item.next])
                    }
                    // 2, 操作的对象可能包含fields
                    if(!item.fields && item.attr) item.fields = []
                    const key = `${item.attr}_${type}`
                    if (item.attr) item.fields = mergeArray(fieldSort[key], item.fields)
                    item.fields?.length && showOperationBtn(item.fields, context, type)
                    // 3, operations 中配置list(按钮下拉)的形式，添加逻辑验证
                    if (item.list) {
                        showOperationBtn(item.list, context)
                    }
                }
            })
        }
        const isTabs = isArray(schema)
        // const isTabs = computed(() => {
            /* 如果传递数组，则是卡片； */
        // const bool = isArray(schema)
        if (isTabs) {
            schema.forEach((item, i) => {
                makeSchema(item, i)
            })
        } else {
            makeSchema(schema, new Date().getTime())
        }
           //  return bool
        // })
        const bindComp = (el, name) => {
            if (!el) return
            widgetExpose[name] = el
        }
        /* 处理各个子组件的通信 */
        const handleUp = async (config, args, cb) => {
            /* 处理子组件emit的事件，并支持回调。若指定方法时，返回执行后的数据，否则返回兄弟组件暴露的所有方法 */
            const { actOn, index, rangeFn } = config
            const fnMap = widgetExpose[`${actOn}Schema`]
            if (!actOn || !fnMap) return
            /* 配置rangeFn，就直接返回整个函数，否则返回整个组件 */
            if (rangeFn) {
                /* 
                    如果第二个参数是函数，就返回第一个参数配置的函数实例，
                    如果是其他（代表是函数实例的参数）就执行后，放在回调函数中返回，也返回执行后的结果
                */
                const method = fnMap[rangeFn]
                if (!method) {
                    throw new Error('调用的组件方法不存在，检查配置文件！')
                } else if (typeof args === 'function') {
                    // 适用于多层调用的情况，比如：翻页查询的是 query 组件的 search 方法，实际又通过 search 方法调用了 table 组件的 update 方法
                    args && args(method, config)
                } else {
                    /* 判断method是否为异步函数 */
                    let result
                    // console.log('判断method是否为异步函数----', result)
                    if (method.constructor.name === 'AsyncFunction') {
                        result = await method(config, args)
                    } else {
                        result = method(config, args)
                        // cb && cb(result)
                    }
                    cb && cb(result)
                    // const result = await method(config, args) // 参数展开后，在调用时可根据需要，传入大对象
                }
            } else {
                const wgt = ref[actOn]?.[index || 0]
                if (cb) cb(wgt)
            }
        }
        const handleFetch = error => {
            emit('fetchError', error)
        }
        const handleEvent = (obj, info) => {
            const data = Object.assign({
                _query: getQueryData(),
                 _table: {
                    length: getTableLength(),
                    selection: getTableSelection(),
                    ...info
                },
                _extra: pageInfo,
            }, obj.data)
            /* 处理子组件emit的事件，并支持回调。若指定方法时，返回执行后的数据，否则返回兄弟组件暴露的所有方法 */
            if (hooks[obj.hookFn]) {
                hooks[obj.hookFn](obj, data)
            } else {
                // console.log('未找到该方法！', obj, data)
                emit('event', obj, data)
            }
        }
        const getQueryData = () => {
            const obj = widgetExpose.querySchema.getData();
            return obj
        }
        const clearQuery = () => {
            widgetExpose.querySchema.clear();
        }
        const refreshTable = () => {
            const obj = widgetExpose.querySchema.search({actOn: 'table', rangeFn: 'update'});
            return obj
        }
        const query = (obj, params) => {
            widgetExpose.querySchema.search(obj, params);
        }
        const closeDialog = (obj) => {
            obj.dialogId && widgetExpose.dialogSchema.close({}, obj, obj.dialogId);
            return obj
        }
        const openDialog = (config, data) => {
            // console.log('openDialog---index', config, data)
            const obj = widgetExpose.dialogSchema.open(config, data);
            return obj
        }
        const getDialogData = () => {
            const obj = widgetExpose.dialogSchema.getData();
            return obj
        }
        const getTableSelection = () => {
            const obj = widgetExpose.tableSchema.getSelection();
            return obj
        }
        const getTableLength = () => {
            const obj = widgetExpose.tableSchema.getLength();
            return obj
        }
        const updateTableData = async (config, data) => {
            await nextTick() // 可能存在动态渲染 v3crud 组件的情况，所以需要等待渲染完成后，再执行
            const obj = widgetExpose.tableSchema.update(config, data);
            return obj
        }
        return {
            handleUp, activeName, isTabs, pageConfig, bindComp, handleFetch, handleEvent,
            closeDialog, openDialog, getDialogData, getQueryData, clearQuery, query,
            getTableSelection, getTableLength, refreshTable, updateTableData
        }
    }
}
</script>
<style scoped>
@charset "UTF-8";

body,header,footer,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,p,th,td,form,fieldset,legend,input,button,textarea,blockquote,hr,pre,section,div,span,a{
    margin:0;padding:0;box-sizing:border-box;
}
body{
    font-family:SourceHanSansCN-Regular;background-color:#f9f9f9;height: 100%;
}
li{
    list-style-type:none;
}
h1,h2,h3,h4,h5,h6,button,input,select{
    font-size:100%;font-weight:400
}
ul,figcaption,figure,footer,header,nav,section,summary{
    display:block
}
i{
    font-style:normal
}
input{
    border:none;
}
html{
    font-size:625%;height:100%;box-sizing:border-box;
}
#app{
    height:100%;
}
*,*:before,*:after{
    box-sizing:inherit;margin:0;padding:0;
}
a,a:focus,a:hover{
    cursor:pointer;color:inherit;outline:none;text-decoration:none;
}
div:focus{
    outline:none;
}
a:focus,a:active{
    outline:none;
}
a,a:focus,a:hover{
    cursor:pointer;color:inherit;text-decoration:none;
}
.clearfix:before{
    content:' ';display:table
}
.clearfix:after{
    content:" ";font-size:0;visibility:hidden;display:block;width:100%;height:0;clear:both
}
.clearfix{
    zoom:1
}
.fl{
    float:left;
}
.fr{
    float:right;
}
.mt10{
    margin-top:10px;
}
*:focus{
    outline:0;
}
*::-webkit-scrollbar {
    width:7px; height:10px; background-color:transparent;
}
 /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
 *::-webkit-scrollbar-track {
    background-color:#f0f0f0;  
} 
/*定义滚动条轨道 内阴影+圆角*/
*::-webkit-scrollbar-thumb {
    background-color:#cdcdcd; border-radius:6px;
}
 /*定义滑块 内阴影+圆角*/
 *{
    scrollbar-color:#cdcdcd #f0f0f0;scrollbar-width: thin;
}
</style>